/** @file         http_client.c
 *  @brief        
 *  @details      http 客户端 demo。
 *  @author       lzm
 *  @date         2021-05-25 15:30:00
 *  @version      v1.0
 *  @copyright    Copyright By lizhuming, All Rights Reserved
 *
 **********************************************************
 *  @LOG 修改日志:
 **********************************************************
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <ctype.h>

/**
  * @name   right_strchr
  * @brief  搜索字符串右边起第一个匹配字符
  * @param  str: 搜索资源
  * @param  c: 目标资源
  * @retval 
  * @author lzm
  */
char *right_strchr(char *str, char c)
{
    int i = strlen(str);
    
    /* [1] check address */
    if(!(*str))
        return 0;

    while(str[i-1])
    {
        if(strchr(str + (i-1), c))
        {
            return (str + (i-1));
        }
        else
        {
            i--;
        }
        if(i==0)
        {
            break;
        }
    }
    return 0;
}


/**
  * @name   str_to_lower
  * @brief  把字符串转换为全小写
  * @param  str: 源资源
  * @retval 
  * @author  lzm
  */
void str_to_lower(char *str)
{
    while(str && *str)
    {
        *str = tolower(*str);
        str++;
    }
}


/**
  * @name   get_connect_info
  * @brief  从字符串 src 中分析出网站地址和端口号，并获得用户下载的文件
  * @param  
  * @retval 
  * @author lzm
  * @note    URL:<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
  */
 void get_connect_info(char *src, char *host, char *file, int *port)
 {
    char *p1;
    char *p2;

    /* 清零 */
    memset(host, 0, sizeof(host));
    memset(file, 0, sizeof(file));

    *port = 0;

    /* 地址验证 */
    if(!(*src))
       return;

    p1 = src;

    /* 检查 URL scheme 字段 */
    if ((!strncmp(p1, "http://", strlen("http://"))))
    {
        p1 = src + strlen("http://");
    }
    else if ((!strncmp(p1, "https://", strlen("https://"))))
    {
        p1 = src + strlen("https://");
    }

    /* 获取 host 段 和 path 段 */
    p2 = strchr(p1, '/'); // 获取域名(host)段尾位置

    if(p2)
    {
        memcpy(host, p1, strlen(p1) - strlen(p2)); // 获取域名（host）段
        
        if(p2 + 1)
        {
            memcpy(file, p2+1, strlen(p2) - 1); // 获取路径（path）段
            file[strlen(p2) - 1] = 0;
        }
    }
    else
    {
        memcpy(host, p1, strlen(p1));
    }
    
    if(p2)
        host[strlen(p1)- strlen(p2)] = 0;
    else
        host[strlen(p1)] = 0;

    p1 = strchr(host, ':');

    if(p1)
        *port = atoi(p1 + 1);
    else
        *port = 80; // 默认端口号
 }



/**
  * @name   main
  * @brief  app
  * @param  
  * @retval 
  * @author lzm
  */
int main(int argc, char *argv[])
{
    int i = 0;
    int sockfd; // 套接字句柄
    int send;
    int totalsend;
    int port;
    int nbytes;
    char *pt;
    char buffer[1024];
    char host_addr[256];
    char host_file[2048];
    char local_file[256];
    char request[1024]; // 请求报文
    struct sockaddr_in server_addr;
    struct hostent *host;
    FILE *fp;

    if(argc != 2)
    {
        fprintf(stderr, "Usage: %s host-address\r\n", argv[0]);
        exit(1);
    }

    str_to_lower(argv[1]); // 把全部参数全部转换为小写

    /* 分析链接，获取链接内容 */
    get_connect_info(argv[1], host_addr, host_file, &port);

    /* 获取主机信息，包括 IP 地址 */
    if((host = gethostbyname(host_addr)) == NULL)
    {
        fprintf(stderr, "get host name error: %s\r\n", strerror(errno));
        exit(1);
    }

    /* 创建 socket */
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) // 采用 IPV4，可靠全双工
    {
        fprintf(stderr, "socket error: %s\r\n", strerror(errno));
        exit(1);
    }

    /* 填充服务端资料 */
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET; // 协议族
    server_addr.sin_port = htons(port); // 端口号
    server_addr.sin_addr = *((struct in_addr *)host->h_addr); // IP 地址

    /* 客户端发起连接请求 */
    if(connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) // 连接上网
    {
        fprintf(stderr, "connect error: %s\r\n", strerror(errno));
        exit(1);
    }

    /* 准备 request 报文 */
    sprintf(request, "GET /%s HTTP/1.1\r\n"
                     "Accept: */*\r\n"
                     "Accept-Language: zh-cn\r\n"
                     "User-Agent: Mozilla/5.0\r\n"
                     "Host: %s:%d\r\n"
                     "Connection: Close\r\n\r\n",
                     host_file, host_addr, port);

    /* 打印 request 报文 */
    printf("%s", request);

    /* 获取真实的文件名 */
    if(host_file && *host_file)
        pt = right_strchr(host_file, '/');
    else
        pt = 0;

    memset(local_file, 0, sizeof(local_file));

    if (pt && *pt)
    {
        if ((pt + 1) && *(pt+1))
            strcpy(local_file, pt + 1);
        else
            memcpy(local_file, host_file, strlen(host_file) - 1);

    } 
    else if (host_file && *host_file) 
    {
        strcpy(local_file, host_file);

    } 
    else 
    {
        strcpy(local_file, "index.html");
    }

    /* 发送 http 请求报文 */
    send = 0;
    totalsend = 0;
    nbytes = strlen(request);

    while(totalsend < nbytes)
    {
        send = write(sockfd, request + totalsend, nbytes - totalsend);
        if(send == -1)
        {
            printf("send error: %s\r\n", strerror(errno));
            exit(0);
        }
        totalsend += send;
    }
    
    fp = fopen(local_file, "w");
    if(!fp)
    {
        printf("create file error: %s\n", strerror(errno));
        return 0;
    }

    /* 连接成功 */
    while((nbytes = read(sockfd, buffer, 1)) == 1)
    {
        /* 报文首部与主体之间有 \r\n\r\n */
        if(i<4)
        {
            if(buffer[i] == '\r' || buffer[i] == '\n')
                i++;
            else
                i = 0;
            
            printf("%c", buffer[i]);
        }
        else
        {
            printf("%c", buffer[i]);
            fwrite(buffer, 1, 1, fp); // 把 http 主体信息写入文件
            i++;
            if(i%1024 == 0)
                fflush(fp); /*每1K时存盘一次*/
        }  
    }
    fclose(fp);
    /* 结束通讯 */
    close(sockfd);
    exit(0);
}


